/*
 * Copyright (C) 2010-2011 Comcast Cable.  All Rights Reserved.
 */
package com.hercules.android.receiver.model.events;

import java.text.MessageFormat;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * <p>
 * This is a base class for all XRE events. It holds common properties for all events: name, source object ID, handler
 * object ID, event phase, index, parameters and timestamp. Note that subclasses of XREEvent store their custom
 * properties in XREEvent#params map.
 * </p>
 * <p>
 * <strong>Thread safety:</strong> This class is mutable and not thread safe.
 * </p>
 *
 * @author saarixx, jmn
 * @version 1.0
 */

public abstract class XREEvent {
    /**
     * <p>
     * The name of the event.
     * </p>
     * <p>
     * Can be any value.
     * </p>
     * <p>
     * Has getter and setter.
     * </p>
     */
    private String name;

    /**
     * <p>
     * The ID of view or resource XRE object on which the event was originally raised.
     * </p>
     * <p>
     * Can be any value.
     * </p>
     * <p>
     * Has getter and setter.
     * </p>
     */
    private int source;

    /**
     * <p>
     * The ID of view or resource XRE object that is handling the event.
     * </p>
     * <p>
     * Can be any value.
     * </p>
     * <p>
     * Has getter and setter.
     * </p>
     */
    private int handler;

    /**
     * <p>
     * The map with event parameters. Keys - parameter names, values - parameter values.
     * </p>
     * <p>
     * Can be any value, can contain any values.
     * </p>
     * <p>
     * Has getter and setter.
     * </p>
     * <p>
     * Is used in getParam() and setParam().
     * </p>
     */
    private Map<String, Object> params = new LinkedHashMap<String, Object>();

    /**
     * <p>
     * The current dispatching phase of this event.
     * </p>
     * <p>
     * Can be any value.
     * </p>
     * <p>
     * Has getter and setter.
     * </p>
     */
    private XREEventPhase phase;

    /**
     * <p>
     * The index of the event in the current session.
     * </p>
     * <p>
     * Can be any value.
     * </p>
     * <p>
     * Has getter and setter.
     * </p>
     */
    private int eventIndex;

    /**
     * <p>
     * The timestamp of the event.
     * </p>
     * <p>
     * Indicates the time the message was sent, in milliseconds since January 1, 1970.
     * </p>
     * <p>
     * Can be any value.
     * </p>
     * <p>
     * Has getter and setter.
     * </p>
     */
    private double timestamp;

    /**
     * <p>
     * Creates new instance of {@link XREEvent} class.
     * </p>
     */
    protected XREEvent() {
        // empty constructor
    }

    /**
     * <p>
     * Retrieves the name of the event.
     * </p>
     *
     * @return the name of the event
     */
    public String getName() {
        return name;
    }

    /**
     * <p>
     * Sets the name of the event.
     * </p>
     *
     * @param name the name of the event
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * <p>
     * Retrieves the ID of view or resource XRE object on which the event was originally raised.
     * </p>
     *
     * @return the ID of view or resource XRE object on which the event was originally raised
     */
    public int getSource() {
        return source;
    }

    /**
     * <p>
     * Sets the ID of view or resource XRE object on which the event was originally raised.
     * </p>
     *
     * @param source the ID of view or resource XRE object on which the event was originally raised
     */
    public void setSource(int source) {
        this.source = source;
    }

    /**
     * <p>
     * Retrieves the ID of view or resource XRE object that is handling the event.
     * </p>
     *
     * @return the ID of view or resource XRE object that is handling the event
     */
    public int getHandler() {
        return handler;
    }

    /**
     * <p>
     * Sets the ID of view or resource XRE object that is handling the event.
     * </p>
     *
     * @param handler the ID of view or resource XRE object that is handling the event
     */
    public void setHandler(int handler) {
        this.handler = handler;
    }

    /**
     * <p>
     * Retrieves the map with event parameters.
     * </p>
     *
     * @return the map with event parameters
     */
    public Map<String, Object> getParams() {
        return params;
    }

    /**
     * <p>
     * Sets the map with event parameters.
     * </p>
     *
     * @param params the map with event parameters
     */
    public void setParams(Map<String, Object> params) {
        this.params = params;
    }

    /**
     * <p>
     * Retrieves the value of the parameter with the given name.
     * </p>
     *
     * @param name the name of the parameter
     * @return the value of the parameter (null if not found)
     * @throws IllegalArgumentException if name is null or empty
     * @throws IllegalStateException if params is null
     */
    public Object getParam(String name) {
        assertParams(name);
        return params.get(name);
    }

    /**
     * <p>
     * Sets the parameter with the given name. If parameter is already specified, overwrites its value.
     * </p>
     *
     * @param name the name of the parameter
     * @param value the new value of the parameter
     * @throws IllegalArgumentException if name is null or empty
     * @throws IllegalStateException if params is null
     */
    public void setParam(String name, Object value) {
        assertParams(name);
        params.put(name, value);
    }

    /**
     * <p>
     * Retrieves the current dispatching phase of this event.
     * </p>
     *
     * @return the current dispatching phase of this event
     */
    public XREEventPhase getPhase() {
        return phase;
    }

    /**
     * <p>
     * Sets the current dispatching phase of this event.
     * </p>
     *
     * @param phase the current dispatching phase of this event
     */
    public void setPhase(XREEventPhase phase) {
        this.phase = phase;
    }

    /**
     * <p>
     * Retrieves the index of the event in the current session.
     * </p>
     *
     * @return the index of the event in the current session
     */
    public int getEventIndex() {
        return eventIndex;
    }

    /**
     * <p>
     * Sets the index of the event in the current session.
     * </p>
     *
     * @param eventIndex the index of the event in the current session
     */
    public void setEventIndex(int eventIndex) {
        this.eventIndex = eventIndex;
    }

    /**
     * <p>
     * Retrieves the timestamp of the event.
     * </p>
     *
     * @return the timestamp of the event
     */
    public double getTimestamp() {
        return timestamp;
    }

    /**
     * <p>
     * Sets the timestamp of the event.
     * </p>
     *
     * @param timestamp the timestamp of the event
     */
    public void setTimestamp(double timestamp) {
        this.timestamp = timestamp;
    }

    /**
     * <p>
     * Retrieves the strongly typed value of the parameter with the given name.
     * </p>
     *
     * @param name the name of the parameter
     * @param clazz the class of the parameter
     * @param <T> the type of the retrieved parameter
     * @return the value of the parameter (null/0/false if not found, depending on the type default value)
     * @throws IllegalArgumentException if name is null or empty
     * @throws IllegalStateException if params is null or the retrieved parameter can not be cast to specified type
     */
    @SuppressWarnings("unchecked")
    <T> T getTypedParam(String name, Class<T> clazz) {

        Object param = getParam(name);

        if (param == null) {
            if (clazz == Boolean.class) {
                return (T) Boolean.FALSE;
            } else if (clazz == Integer.class) {
                return (T) new Integer(0);
            } else if (clazz == Double.class) {
                return (T) new Double(0);
            }

            return null;
        }

        if (!clazz.isAssignableFrom(param.getClass())) {
            throw new IllegalStateException(MessageFormat.format(
                "The type of parameter {0} can not be converted into {1}.", name, clazz.getName()));
        }

        return (T) param;
    }

    /**
     * <p>
     * Asserts that the params field is not null. Also asserts that the passed name is not null and not empty string.
     * </p>
     * <p>
     * AndroidAssembly1 merged with assertName method, changed exceptions
     *
     * @param name the name of the parameter to assert
     * @throws IllegalStateException if params is null
     * @throws IllegalArgumentException if name is null or empty
     */
    private void assertParams(String name) {
        if (name == null || name.trim().length() == 0) {
            throw new IllegalArgumentException("Parameter 'name' can not be null or empty string.");
        }
        if (params == null) {
            throw new IllegalStateException("Property 'params' must be not null.");
        }
    }
}
